﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using RoMBot;
using RoMBot.Classes;
using RoMBot.Database;
using RoMBot.Waypoints;
using Key = System.Windows.Input.Key;
using Keyboard = System.Windows.Input.Keyboard;

namespace RomBotUI.Map
{
    /// <summary>
    ///  Realice los pasos 1a o 1b y luego 2 para usar este control personalizado en un archivo XAML.
    ///
    /// Paso 1a) Usar este control personalizado en un archivo XAML existente en el proyecto actual.
    /// Agregue este atributo XmlNamespace al elemento raíz del archivo de marcado en el que 
    /// se va a utilizar:
    ///
    ///     xmlns:MyNamespace="clr-namespace:RomBotUI.Map"
    ///
    ///
    /// Paso 1b) Usar este control personalizado en un archivo XAML existente en otro proyecto.
    /// Agregue este atributo XmlNamespace al elemento raíz del archivo de marcado en el que 
    /// se va a utilizar:
    ///
    ///     xmlns:MyNamespace="clr-namespace:RomBotUI.Map;assembly=RomBotUI.Map"
    ///
    /// Tendrá también que agregar una referencia de proyecto desde el proyecto en el que reside el archivo XAML
    /// hasta este proyecto y recompilar para evitar errores de compilación:
    ///
    ///     Haga clic con el botón secundario del mouse en el proyecto de destino en el Explorador de soluciones y seleccione
    ///     "Agregar referencia"->"Proyectos"->[Busque y seleccione este proyecto]
    ///
    ///
    /// Paso 2)
    /// Prosiga y utilice el control en el archivo XAML.
    ///
    ///     <MyNamespace:Map/>
    ///
    /// </summary>
    public class Map : FrameworkElement
    {
        private VisualCollection _children;
        static PointCollection myPointCollection = new PointCollection();
        Point firstPoint = new Point();
        private bool first = true;
        StreamGeometry streamGeometry = new StreamGeometry();
        ObjectsTable surround = new ObjectsTable();
        private static Pen trazo = new Pen( Brushes.Black, 0.5 );
        DrawingVisual visual = new DrawingVisual();
        ScaleTransform escala = new ScaleTransform( 0.5, 0.5, 0.5, 0.5 );
        private double pathScale = 10d;
        //private double minX = 0;
        //private double minY = 0;
        private static List<Drawing> hits = new List<Drawing>();
        private Dictionary<Point, GameObject> mobs = new Dictionary<Point, GameObject>();
        private Dictionary<Point, GameObject> npcs = new Dictionary<Point, GameObject>();
        private Dictionary<Point, GameObject> resources = new Dictionary<Point, GameObject>();

        static Map()
        {
            //DefaultStyleKeyProperty.OverrideMetadata( typeof( Map ), new FrameworkPropertyMetadata( typeof( Map ) ) );
        }

        public Map()
        {
            _children = new VisualCollection( this );
            this._children.Add( this.visual );

            this.PreviewMouseLeftButtonUp += this.MapMouseLeftButtonUp;
            ////this.PreviewMouseRightButtonUp += OnMouseRightButtonUp;
            ////this.PreviewDragLeave += OnPreviewDragLeave;
            //this.HorizontalAlignment = HorizontalAlignment.Stretch;
            //this.VerticalAlignment = VerticalAlignment.Stretch;
            //this.RenderTransform = escala;
        }

        // Capture the mouse event and hit test the coordinate point value against
        // the child visual objects.
        void MapMouseLeftButtonUp( object sender, MouseButtonEventArgs e )
        {
            if ( Keyboard.IsKeyDown( Key.LeftCtrl ) || Keyboard.IsKeyDown( Key.RightCtrl ) )
            {
                return;
            }

            hits.Clear();
            // Retreive the coordinates of the mouse button event.
            var pt = e.GetPosition( ( UIElement ) sender );
            if ( VisualTreeHelper.HitTest( visual, pt ) != null )
            {
                HitTestGeometryInVisual( visual, pt );
            }
            if ( hits.Any( h => h.Bounds.Contains( firstPoint ) || myPointCollection.Any( p => h.Bounds.Contains( p ) ) ) )
            {
                Bot.Debug( "Waypoint clicked!" );
            }
            if ( hits.Any( h => mobs.Any( m => h.Bounds.Contains( m.Key ) ) ) )
            {
                var mob = hits.Select( ( h => mobs.FirstOrDefault( m => h.Bounds.Contains( m.Key ) ) ) ).FirstOrDefault();
                Bot.Printf( "Mob clicked name:{0}", mob.Value.Name );
            }
            // Initiate the hit test by setting up a hit test result callback method.
            //VisualTreeHelper.HitTest( visual, null, this.MyCallback, new PointHitTestParameters( pt ) );
        }

        internal Point PlayerPoint
        {
            get
            {
                return new Point( Math.Abs( Bot.Player.X ) / pathScale, Math.Abs( Bot.Player.Z ) / pathScale );
            }
        }

        public static readonly DependencyProperty ShowResourcesProperty =
          DependencyProperty.Register( "ShowResources", typeof( bool ),
          typeof( Map ), new UIPropertyMetadata( true ) );

        public bool ShowResources
        {
            get { return ( bool ) GetValue( ShowResourcesProperty ); }
            set { SetValue( ShowResourcesProperty, value ); }
        }

        public static readonly DependencyProperty ShowNpcsProperty =
          DependencyProperty.Register( "ShowNpcs", typeof( bool ),
          typeof( Map ), new UIPropertyMetadata( true ) );

        public bool ShowNpcs
        {
            get { return ( bool ) GetValue( ShowNpcsProperty ); }
            set { SetValue( ShowNpcsProperty, value ); }
        }

        public static readonly DependencyProperty ShowMobsProperty =
          DependencyProperty.Register( "ShowMobs", typeof( bool ),
          typeof( Map ), new UIPropertyMetadata( true ) );

        public bool ShowMobs
        {
            get { return ( bool ) GetValue( ShowMobsProperty ); }
            set { SetValue( ShowMobsProperty, value ); }
        }

        public static readonly DependencyProperty IconScaleProperty =
          DependencyProperty.Register( "IconScale", typeof( double ),
          typeof( Map ), new UIPropertyMetadata( 1d ) );

        public double IconScale
        {
            get { return ( double ) GetValue( IconScaleProperty ); }
            set { SetValue( IconScaleProperty, value ); }
        }

        public void IsHitTest( Point pt )
        {
            HitTestGeometryInVisual( visual, pt );
        }

        internal ScaleTransform Escala
        {
            get { return escala; }
        }

        internal DrawingVisual Visual
        {
            get { return visual; }
        }

        // Determine if a geometry within the visual was hit. 
        static public void HitTestGeometryInVisual( Visual visual, Point pt )
        {
            // Retrieve the group of drawings for the visual.
            var drawingGroup = VisualTreeHelper.GetDrawing( visual );
            EnumDrawingGroup( drawingGroup, pt );
        }

        // Enumerate the drawings in the DrawingGroup. 
        static public void EnumDrawingGroup( DrawingGroup drawingGroup, Point pt )
        {
            var drawingCollection = drawingGroup.Children;

            // Enumerate the drawings in the DrawingCollection. 
            foreach ( var drawing in drawingCollection )
            {
                // If the drawing is a DrawingGroup, call the function recursively. 
                if ( drawing is DrawingGroup )
                {
                    EnumDrawingGroup( ( DrawingGroup ) drawing, pt );
                }
                else if ( drawing is GeometryDrawing )
                {
                    // Determine whether the hit test point falls within the geometry. 
                    if ( ( ( GeometryDrawing ) drawing ).Geometry.FillContains( pt ) )
                    {
                        if ( ( ( GeometryDrawing ) drawing ).Geometry is EllipseGeometry )
                        {
                            hits.Add( drawing );
                            Bot.Debug( "Hit test En ellipse!!!" );
                        }
                        else
                        {
                            Bot.Debug( "Hit test (dentro)" );
                        }
                    }
                    else if ( ( ( GeometryDrawing ) drawing ).Geometry.StrokeContains( trazo, pt ) )
                    {
                        Bot.Debug( "Hit test (trazo)" );
                    }
                }
            }
        }

        // If a child visual object is hit, toggle its opacity to visually indicate a hit.
        public HitTestResultBehavior MyCallback( HitTestResult result )
        {
            if ( result.VisualHit.GetType() == typeof( DrawingVisual ) )
            {
                if ( ( ( DrawingVisual ) result.VisualHit ).Opacity == 1.0 )
                {
                    ( ( DrawingVisual ) result.VisualHit ).Opacity = 0.4;
                }
                else
                {
                    ( ( DrawingVisual ) result.VisualHit ).Opacity = 1.0;
                }
            }

            // Stop the hit test enumeration of objects in the visual tree.
            return HitTestResultBehavior.Stop;
        }
        public void Draw()
        {
            visual.Children.Clear();
            //if ( first )
            {
                Monitor.Enter( Bot.Instancia.WaypointList.Waypoints );
                var lista = new Waypoint[ Bot.Instancia.WaypointList.Waypoints.Count ];
                Bot.Instancia.WaypointList.Waypoints.CopyTo( lista );
                Monitor.Exit( Bot.Instancia.WaypointList.Waypoints );
                //minX = lista.Min( p => Math.Abs( ( double ) p.X ) );
                //minY = lista.Min( p => Math.Abs( ( double ) p.Z ) );
                //var maxX = lista.Max( p => Math.Abs( ( double ) p.X ) );
                //var maxY = lista.Max( p => Math.Abs( ( double ) p.Z ) );

                foreach ( var punto in lista )
                {
                    myPointCollection.Add( new Point( Math.Abs( ( double ) punto.X ) / pathScale, Math.Abs( ( double ) punto.Z ) / pathScale ) );
                }
                firstPoint = myPointCollection.First();
                myPointCollection.RemoveAt( 0 );
                DrawPath();

                //escala.CenterX = maxX - minX;
                //escala.CenterY = maxY - minY;
                first = false;
            }
            var currentPoint = new Point( Math.Abs( ( double ) Bot.Instancia.WaypointList.Current.X ) / pathScale, Math.Abs( ( double ) Bot.Instancia.WaypointList.Current.Z ) / pathScale );
            using ( var context = visual.RenderOpen() )
            {

                context.DrawGeometry( Brushes.Transparent, trazo, streamGeometry );
                context.DrawEllipse( firstPoint == currentPoint ? Brushes.Blue : Brushes.DarkRed, null, firstPoint, 2d * IconScale, 2d * IconScale );
                foreach ( var point in myPointCollection )
                {
                    if ( point == currentPoint )
                    {
                        context.DrawEllipse( Brushes.MediumBlue, null, point, 3d * IconScale, 3d * IconScale );
                    }
                    else
                    {
                        context.DrawEllipse( Brushes.DarkRed, null, point, 2d * IconScale, 2d * IconScale );
                    }
                }
                Monitor.Enter( Bot.Player );
                Monitor.Exit( Bot.Player );
                context.DrawEllipse( Brushes.DarkGreen, null, new Point( Math.Abs( ( double ) Bot.Player.X ) / pathScale, Math.Abs( ( double ) Bot.Player.Z ) / pathScale ), 3.5d * IconScale, 3.5d * IconScale );
                surround.Update();

                mobs.Clear();
                if ( ShowMobs )
                {
                    var gameMobs = surround.Where( o => o.Type == ObjectType.Monster && RoMBot.Helpers.Distance( Bot.Player.X, Bot.Player.Z, o.X, o.Z ) < 500 );
                    foreach ( var mob in gameMobs )
                    {
                        var mobPt = new Point( Math.Abs( ( double ) mob.X ) / pathScale, Math.Abs( ( double ) mob.Z ) / pathScale );
                        //mobs.Add( mobPt, mob );
                        context.DrawEllipse( mob.Aggressive ? Brushes.Red : Brushes.DarkGoldenrod, null, mobPt, 2.5d * IconScale, 2.5d * IconScale );
                    }
                }
                if ( ShowResources )
                {
                    var recursos = surround.Where( o => o.Type == ObjectType.NpcOrNode && RoMBot.Helpers.Distance( Bot.Player.X, Bot.Player.Z, o.X, o.Z ) < 500 && Database.Nodes.ContainsKey( o.Id ) );
                    foreach ( var rec in recursos )
                    {
                        context.DrawEllipse( Database.Nodes[ rec.Id ].Type == NodeType.Herb ? Brushes.LawnGreen : Database.Nodes[ rec.Id ].Type == NodeType.Ore ? Brushes.Gold : Brushes.Brown, null, new Point( Math.Abs( ( double ) rec.X ) / pathScale, Math.Abs( ( double ) rec.Z ) / pathScale ), 2.5d * IconScale, 2.5d * IconScale );
                    }
                }
                if ( ShowNpcs )
                {
                    var npcs = surround.Where( o => o.Type == ObjectType.NpcOrNode && RoMBot.Helpers.Distance( Bot.Player.X, Bot.Player.Z, o.X, o.Z ) < 500 && !Database.Nodes.ContainsKey( o.Id ) && !o.Attackable );
                    foreach ( var npc in npcs )
                    {
                        context.DrawEllipse( Brushes.SlateGray, null, new Point( Math.Abs( ( double ) npc.X ) / pathScale, Math.Abs( ( double ) npc.Z ) / pathScale ), 5d * IconScale, 5d * IconScale );
                    }
                }
            }
        }

        private DrawingVisual CreateDrawingVisualRectangle()
        {
            DrawingVisual drawingVisual = new DrawingVisual();

            // Retrieve the DrawingContext in order to create new drawing content.
            DrawingContext drawingContext = drawingVisual.RenderOpen();

            // Create a rectangle and draw it in the DrawingContext.
            Rect rect = new Rect( new System.Windows.Point( 160, 100 ), new System.Windows.Size( 320, 80 ) );
            drawingContext.DrawRectangle( System.Windows.Media.Brushes.LightBlue, ( System.Windows.Media.Pen ) null, rect );

            // Persist the drawing content.
            drawingContext.Close();

            return drawingVisual;
        }

        private void DrawPath()
        {
            streamGeometry.Clear();
            using ( var geometryContext = streamGeometry.Open() )
            {
                geometryContext.BeginFigure( firstPoint, true, false );
                geometryContext.PolyLineTo( myPointCollection, true, true );
            }
        }

        //Provide a required override for the VisualChildrenCount property.
        protected override int VisualChildrenCount
        {
            get { return _children.Count; }
        }

        public List<Drawing> Hits
        {
            get { return hits; }
        }

        protected override Visual GetVisualChild( int index )
        {
            if ( index < 0 || index >= _children.Count )
            {
                throw new ArgumentOutOfRangeException();
            }

            return _children[ index ];
        }
    }
}
